/*
 * Project: Sudoku Explainer
 * Copyright (C) 2006-2007 Nicolas Juillerat
 * 
 * Project: Sudoku Hint Engine for Android
 * Copyright (C) 2010-2013 Emanuel Moecklin
 * 
 * Available under the terms of the Lesser General Public License (LGPL)
 */
package com.onegravity.sudoku.solver.rules.hidden;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import android.net.Uri;

import com.onegravity.sudoku.solver.HintResource;
import com.onegravity.sudoku.solver.HintType;
import com.onegravity.sudoku.solver.R;
import com.onegravity.sudoku.solver.core.HasParentPotentialHint;
import com.onegravity.sudoku.solver.core.IndirectHint;
import com.onegravity.sudoku.solver.core.Potential;
import com.onegravity.sudoku.solver.core.Rule;
import com.onegravity.sudoku.solver.data.Cell;
import com.onegravity.sudoku.solver.data.ExtraRegionExtended;
import com.onegravity.sudoku.solver.data.Grid;
import com.onegravity.sudoku.solver.data.ImmutableCell;
import com.onegravity.sudoku.solver.data.Region;
import com.onegravity.sudoku.solver.util.HtmlLoader;

/**
 * Hidden set hint
 */
public class HiddenSubsetHint extends IndirectHint implements Rule, HasParentPotentialHint {

	private static final long serialVersionUID = -2657042396314409080L;
	
	private final Cell[] cells;
    private final int[] values;
    private final Map<Cell, BitSet> highlightPotentials;
    private final Region region;
    
    public HiddenSubsetHint(Cell[] cells, int[] values, Map<Cell, BitSet> highlightPotentials, Map<Cell, BitSet> removePotentials, Region region) {
        super(removePotentials);
        this.cells = cells;
        this.values = values;
        this.region = region;

        // precompute candidates to highlight
        Map<Cell, BitSet> result = new HashMap<Cell, BitSet>();
        for (Cell cell : highlightPotentials.keySet()) {
            BitSet cand = (BitSet) cell.getPotentialValues().clone();
            cand.and(highlightPotentials.get(cell));
            result.put(cell, cand);
        }
        this.highlightPotentials = result;
    }

    @Override
    public Collection<Cell> getSelectedCells(HintType hintType) {
        return null;
    }

    @Override
    public Map<ImmutableCell, BitSet> getRemovedPotentials(HintType hintType) {
        return (hintType==HintType.HINT) ? super.getRemovedPotentials(hintType) : null;
    }

    @Override
    public Map<Cell, BitSet> getSelectedPotentials(HintType hintType, int viewNum) {
        return (hintType!=HintType.SMALL_CLUE) ? highlightPotentials : null;
    }

    @Override
    public Region[] getRegions(HintType hintType) {
        return new Region[] {this.region};
    }

    @Override
    public double getDifficulty() {
        int degree = values.length;
    	double add4ExtraRegion = (region instanceof ExtraRegionExtended) ? 0.2 : 0.0;
        return ((degree==2) ? 3.4 : (degree==3) ? 4.0 : 5.4) + add4ExtraRegion;
    }

    @Override
    public String getName() {
        int degree = values.length;
    	HintResource hintRes = (degree==2) ? HintResource.HIDDEN_PAIR : (degree==3) ? HintResource.HIDDEN_TRIPLE : HintResource.HIDDEN_QUAD;
        return hintRes.getName();
    }

    @Override
    public Uri getLink() {
        int degree = values.length;
    	HintResource hintRes = (degree==2) ? HintResource.HIDDEN_PAIR : (degree==3) ? HintResource.HIDDEN_TRIPLE : HintResource.HIDDEN_QUAD;
        return hintRes.getUrl();
	}

    public Collection<Potential> getRuleParents(Grid initialGrid, Grid currentGrid) {
        Collection<Potential> result = new ArrayList<Potential>();
        BitSet myPositions = new BitSet(9);
        BitSet tmp = new BitSet(9);
        for (int i = 0; i < values.length; i++) {
        	region.getPotentialPositions(tmp, values[i]);
            myPositions.or(tmp);
        }
        for (int i = 0; i < 9; i++) {
            if (!myPositions.get(i)) {
                Cell cell = region.getCell(i);
                Cell initialCell = initialGrid.getCell(cell.getX(), cell.getY());
                for (int j = 0; j < values.length; j++) {
                    if (initialCell.hasPotentialValue(values[j])) {
                        // This potential must go off before I can be applied
                        result.add(new Potential(cell, values[j], false));
                    }
                }
            }
        }
        return result;
    }

    @Override
    public String getHintHtml(HintType type) {
        int degree = values.length;
    	HintResource hintRes = (degree==2) ? HintResource.HIDDEN_PAIR :  (degree==3) ? HintResource.HIDDEN_TRIPLE : HintResource.HIDDEN_QUAD;
        String result = HtmlLoader.loadHtml(hintRes.hint(type));

        final int[] numberIds = new int[] {R.string.two, R.string.three, R.string.four};
        String counter = HintResource.getString( numberIds[values.length - 2] );
        String cellList = HtmlLoader.formatList(cells);
        String valueList = HtmlLoader.formatValues(values);
        String regionName = region.toFullString();
        String ruleName = getName();
    	String link = (degree==2) ? "Hidden_Pair" : (degree==3) ? "Hidden_Triple" : "Hidden_Quad"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        return HtmlLoader.format(result, counter, cellList, valueList, regionName, ruleName, link);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(getName());
        builder.append(": "); //$NON-NLS-1$
        if (cells.length <= 4)
            builder.append(Cell.toFullString(this.cells));
        else
            builder.append("Cells [...]"); //$NON-NLS-1$
        builder.append(": "); //$NON-NLS-1$
        for (int i = 0; i < values.length; i++) {
            if (i > 0)
                builder.append(","); //$NON-NLS-1$
            builder.append(Integer.toString(values[i]));
        }
        builder.append(" in "); //$NON-NLS-1$
        builder.append(region.toString());
        return builder.toString();
    }

}